///////////////////////////////////////////////////////////////////////////////
//
//mmMatrix.h
// 
// Author:
//      Mark Hobbs mark@hobsie.com
// 
// Copyright (c) 2010 Mark Hobbs
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
///////////////////////////////////////////////////////////////////////////////

#ifndef _MM_MATRIX_H_
#define _MM_MATRIX_H_

#include "mmVector3.h"

namespace MM
{
	
#ifdef _MSC_VER
__declspec(align(16)) class Matrix
#else
class Matrix
#endif
{
public:

	union
	{
		struct
		{
			float _11, _12, _13, _14;
			float _21, _22, _23, _24;
			float _31, _32, _33, _34;
			float _41, _42, _43, _44;
		};
		float _f[16];
		float _m[4][4];
	};
	
	inline Matrix();
	inline Matrix(float input11, float input12, float input13, float input14,
				  float input21, float input22, float input23, float input24,
				  float input31, float input32, float input33, float input34,
				  float input41, float input42, float input43, float input44);
	inline Matrix(const Matrix &matrix);
	inline Matrix(const Vector3 &v1, const Vector3 &v2, const Vector3 &v3);
	
    inline Matrix operator + (const Matrix &matrix);

    inline Matrix operator * (const Matrix &matrix);
    
    inline Matrix operator / (float f);

    inline void operator *= (float f);
    inline void operator /= (float f);

    inline float getDeterminant();
    inline void  invert();
	
#ifdef __GNUC__
} __attribute__ ((aligned(16)));
#else
}; // class Matrix
#endif

// Inline function declarations

inline Matrix::Matrix()
{
}
		
inline Matrix::Matrix(float input11, float input12, float input13, float input14,
				  	  float input21, float input22, float input23, float input24,
				  	  float input31, float input32, float input33, float input34,
				  	  float input41, float input42, float input43, float input44)
{
    _11 = input11;
    _12 = input12;
    _13 = input13;
    _14 = input14;
    
    _21 = input21;
    _22 = input22;
    _23 = input23;
    _24 = input24;
    
    _31 = input31;
    _32 = input32;
    _33 = input33;
    _34 = input34;
    
    _41 = input41;
    _42 = input42;
    _43 = input43;
    _44 = input44;
}

inline Matrix::Matrix(const Matrix &matrix)
{
    _11 = matrix._11;
    _12 = matrix._12;
    _13 = matrix._13;
    _14 = matrix._14;
    
    _21 = matrix._21;
    _22 = matrix._22;
    _23 = matrix._23;
    _24 = matrix._24;
    
    _31 = matrix._31;
    _32 = matrix._32;
    _33 = matrix._33;
    _34 = matrix._34;
    
    _41 = matrix._41;
    _42 = matrix._42;
    _43 = matrix._43;
    _44 = matrix._44;
}

inline Matrix::Matrix(const Vector3 &v1, const Vector3 &v2, const Vector3 &v3)
{
    _11 = v1.x;
    _12 = v1.y;
    _13 = v1.z;
    _14 = 0.0f;
    
    _21 = v2.x;
    _22 = v2.y;
    _23 = v2.z;
    _24 = 0.0f;
    
    _31 = v3.x;
    _32 = v3.y;
    _33 = v3.z;
    _34 = 0.0f;
    
    _41 = 0.0f;
    _42 = 0.0f;
    _43 = 0.0f;
    _44 = 1.0f;    // bottom right value set to 1
}

inline Matrix Matrix::operator + (const Matrix &matrix)
{
    Matrix temp;    // return matrix

    // standard matrix addition of two matrices
    temp._11 = _11+matrix._11;
    temp._12 = _12+matrix._12;
    temp._13 = _13+matrix._13;
    temp._14 = _14+matrix._14;

    temp._21 = _21+matrix._21;
    temp._22 = _22+matrix._22;
    temp._23 = _23+matrix._23;
    temp._24 = _24+matrix._24;

    temp._31 = _31+matrix._31;
    temp._32 = _32+matrix._32;
    temp._33 = _33+matrix._33;
    temp._34 = _34+matrix._34;

    temp._41 = _41+matrix._41;
    temp._42 = _42+matrix._42;
    temp._43 = _43+matrix._43;
    temp._44 = _44+matrix._44;

    return(temp);
}

inline Matrix Matrix::operator * (const Matrix &matrix)
{
    Matrix temp;    // return matrix

    // Standard matrix multiplication of two matrices
    temp._11 = (_11*matrix._11)+(_12*matrix._21)+(_13*matrix._31)+(_14*matrix._41);
    temp._12 = (_11*matrix._12)+(_12*matrix._22)+(_13*matrix._32)+(_14*matrix._42);
    temp._13 = (_11*matrix._13)+(_12*matrix._23)+(_13*matrix._33)+(_14*matrix._43);
    temp._14 = (_11*matrix._14)+(_12*matrix._24)+(_13*matrix._34)+(_14*matrix._44);

    temp._21 = (_21*matrix._11)+(_22*matrix._21)+(_23*matrix._31)+(_24*matrix._41);
    temp._22 = (_21*matrix._12)+(_22*matrix._22)+(_23*matrix._32)+(_24*matrix._42);
    temp._23 = (_21*matrix._13)+(_22*matrix._23)+(_23*matrix._33)+(_24*matrix._43);
    temp._24 = (_21*matrix._14)+(_22*matrix._24)+(_23*matrix._34)+(_24*matrix._44);

    temp._31 = (_31*matrix._11)+(_32*matrix._21)+(_33*matrix._31)+(_34*matrix._41);
    temp._32 = (_31*matrix._12)+(_32*matrix._22)+(_33*matrix._32)+(_34*matrix._42);
    temp._33 = (_31*matrix._13)+(_32*matrix._23)+(_33*matrix._33)+(_34*matrix._43);
    temp._34 = (_31*matrix._14)+(_32*matrix._24)+(_33*matrix._34)+(_34*matrix._44);

    temp._41 = (_41*matrix._11)+(_42*matrix._21)+(_43*matrix._31)+(_44*matrix._41);
    temp._42 = (_41*matrix._12)+(_42*matrix._22)+(_43*matrix._32)+(_44*matrix._42);
    temp._43 = (_41*matrix._13)+(_42*matrix._23)+(_43*matrix._33)+(_44*matrix._43);
    temp._44 = (_41*matrix._14)+(_42*matrix._24)+(_43*matrix._34)+(_44*matrix._44);

    return(temp);
}

inline Matrix Matrix::operator / (float f)
{
	Matrix temp;    // return matrix
	
    temp._11 = _11 / f;
    temp._12 = _12 / f;
    temp._13 = _13 / f;
    temp._14 = _14 / f;
    
    temp._21 = _21 / f;
    temp._22 = _22 / f;
    temp._23 = _23 / f;
    temp._24 = _24 / f;
    
    temp._31 = _31 / f;
    temp._32 = _32 / f;
    temp._33 = _33 / f;
    temp._34 = _34 / f;
    
    temp._41 = _41 / f;
    temp._42 = _42 / f;
    temp._43 = _43 / f;
    temp._44 = _44 / f;

    return(temp);
}

inline void Matrix::operator *= (float f)
{
    _11 *= f;
    _12 *= f;
    _13 *= f;
    _14 *= f;
    
    _21 *= f;
    _22 *= f;
    _23 *= f;
    _24 *= f;
    
    _31 *= f;
    _32 *= f;
    _33 *= f;
    _34 *= f;
    
    _41 *= f;
    _42 *= f;
    _43 *= f;
    _44 *= f;
}

inline void Matrix::operator /= (float f)
{
    _11 /= f;
    _12 /= f;
    _13 /= f;
    _14 /= f;
    
    _21 /= f;
    _22 /= f;
    _23 /= f;
    _24 /= f;
    
    _31 /= f;
    _32 /= f;
    _33 /= f;
    _34 /= f;
    
    _41 /= f;
    _42 /= f;
    _43 /= f;
    _44 /= f;
}


inline float Matrix::getDeterminant()
{
  float det;

  det = _m[0][0]*(_m[1][1]*_m[2][2] - _m[1][2]*_m[2][1]) -
    _m[0][1]*(_m[1][0]*_m[2][2] - _m[1][2]*_m[2][0]) +
    _m[0][2]*(_m[1][0]*_m[2][1] - _m[1][1]*_m[2][0]);

  return det;
}

inline void Matrix::invert()
{
  float det = getDeterminant();

  _m[0][0] = _m[1][1]*_m[2][2] - _m[1][2]*_m[2][1];
  _m[0][1] = _m[1][0]*_m[2][2] - _m[1][2]*_m[2][0];
  _m[0][2] = _m[1][0]*_m[2][1] - _m[1][1]*_m[2][0];

  _m[1][0] = _m[0][1]*_m[2][2] - _m[0][2]*_m[2][1];
  _m[1][1] = _m[0][0]*_m[2][2] - _m[0][2]*_m[2][0];
  _m[1][2] = _m[0][0]*_m[2][1] - _m[0][1]*_m[2][0];

  _m[2][0] = _m[0][1]*_m[1][2] - _m[0][2]*_m[1][1];
  _m[2][1] = _m[0][0]*_m[1][2] - _m[0][2]*_m[1][0];
  _m[2][2] = _m[0][0]*_m[1][1] - _m[0][1]*_m[1][0];

  *this = *this / det;
}
	
} // namespace MM

#endif // _MM_MATRIX_H_